//
//  UILabel+SLLinke.m
//  可点击的 Label
//
//  Created by zhusl on 2019/5/16.
//  Copyright © 2019年 com_zhusl. All rights reserved.
//

#import "UILabel+SLLinke.h"
#import <objc/runtime.h>

static char sl_linkeDelegateKey, linkeRangsKey;

@implementation UILabel (SLLinke)

- (id)sl_linkeDelegate {
    return objc_getAssociatedObject(self, &sl_linkeDelegateKey);
}

- (void)setSl_linkeDelegate:(id)sl_linkeDelegate {

    objc_setAssociatedObject(self, &sl_linkeDelegateKey, sl_linkeDelegate, OBJC_ASSOCIATION_ASSIGN);
}

- (id)linkeRangs {
    return objc_getAssociatedObject(self, &linkeRangsKey);
}

- (void)setLinkeRangs:(id)linkeRangs {

    objc_setAssociatedObject(self, &linkeRangsKey, linkeRangs, OBJC_ASSOCIATION_RETAIN);
}

- (void)addlinkeString:(NSString *)string withRange:(NSRange)range{
    
    if (!self.linkeRangs) {
        self.linkeRangs = [[NSMutableArray alloc] init];
    }
    
    SLLabelLinkeModel * linkeModel = [[SLLabelLinkeModel alloc] init];
    linkeModel.range = range;
    linkeModel.string = string;
    
    [self.linkeRangs addObject:linkeModel];
    
    self.userInteractionEnabled = YES;
    self.multipleTouchEnabled = YES;
}

- (void)addlinkeString:(NSString *)string{
    
    [self addlinkeString:string withRange:[self.attributedText.string rangeOfString:string]];
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch=[touches anyObject];
    CGPoint point=[touch locationInView:self];
    
    CFIndex idx=[self characterIndexAtPoint:point];
    
    if (idx>=0) {
        if ([self.sl_linkeDelegate respondsToSelector:@selector(sl_labelLinkeClicked:withIndex:withRange:)]) {

            NSRange range = NSMakeRange(idx, 1);
            
            for (int idex = 0;idex < self.linkeRangs.count;idex++) {
                SLLabelLinkeModel *linkeModel = self.linkeRangs[idex];
                if (range.location>=linkeModel.range.location&&range.location<linkeModel.range.location+linkeModel.range.length ) {
                    [self.sl_linkeDelegate sl_labelLinkeClicked:linkeModel.string withIndex:idex withRange:linkeModel.range];
                    break;
                }
            }
        }
    }
}

- (CFIndex)characterIndexAtPoint:(CGPoint)p {
    CFIndex idx = -1;
    
    if (!CGRectContainsPoint(self.bounds, p)) {
        return idx;
    }
    
    CGRect textRect = [self sl_textRectForBounds:self.bounds limitedToNumberOfLines:self.numberOfLines];
    if (!CGRectContainsPoint(textRect, p)) {
        return idx;
    }
    
    // Offset tap coordinates by textRect origin to make them relative to the origin of frame
    p = CGPointMake(p.x - textRect.origin.x, p.y - textRect.origin.y);
    // Convert tap coordinates (start at top left) to CT coordinates (start at bottom left)
    p = CGPointMake(p.x, textRect.size.height - p.y);
    
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, textRect);
    
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self.attributedText);
    
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, (CFIndex)[self.attributedText length]), path, NULL);
    if (frame == NULL) {
        CGPathRelease(path);
        return idx;
    }
    
    CFArrayRef lines = CTFrameGetLines(frame);
    NSInteger numberOfLines = (self.numberOfLines > 0 ? MIN(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines));
    if (numberOfLines == 0) {
        CFRelease(frame);
        CGPathRelease(path);
        return idx;
    }
    
    CGPoint lineOrigins[numberOfLines];
    CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins);
    
    for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) {
        CGPoint lineOrigin = lineOrigins[lineIndex];
        //lineOrigin.y-=(numberOfLines-1)*[self lineSp];
        CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);
        
        // Get bounding information of line
        CGFloat ascent = 0.0f, descent = 0.0f, leading = 0.0f;
        CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
        CGFloat yMin = (CGFloat)floor(lineOrigin.y - descent);
        CGFloat yMax = (CGFloat)ceil(lineOrigin.y + ascent);
        // Apply penOffset using flushFactor for horizontal alignment to set lineOrigin since this is the horizontal offset from drawFramesetter
        CGFloat flushFactor = TTTFlushFactorForTextAlignment(self.textAlignment);
        CGFloat penOffset = (CGFloat)CTLineGetPenOffsetForFlush(line, flushFactor, textRect.size.width);
        lineOrigin.x = penOffset;
        
        // Check if we've already passed the line
        if (p.y > yMax) {
            break;
        }
        // Check if the point is within this line vertically
        if (p.y >= yMin) {
            // Check if the point is within this line horizontally
            if (p.x >= lineOrigin.x && p.x <= lineOrigin.x + width) {
                // Convert CT coordinates to line-relative coordinates
                CGPoint relativePoint = CGPointMake(p.x - lineOrigin.x, p.y - lineOrigin.y);
                idx = CTLineGetStringIndexForPosition(line, relativePoint);
                break;
            }
        }
        
    }
    CFRelease(framesetter);
    CFRelease(frame);
    CGPathRelease(path);
    
    return idx;
}

- (CGRect)sl_textRectForBounds:(CGRect)bounds
     limitedToNumberOfLines:(NSInteger)numberOfLines
{
    bounds = UIEdgeInsetsInsetRect(bounds, UIEdgeInsetsZero);
    if (!self.attributedText) {
        return [self  textRectForBounds:bounds limitedToNumberOfLines:numberOfLines];
    }
    
    CGRect textRect = bounds;
    
    // Calculate height with a minimum of double the font pointSize, to ensure that CTFramesetterSuggestFrameSizeWithConstraints doesn't return CGSizeZero, as it would if textRect height is insufficient.
    textRect.size.height = MAX(self.font.lineHeight * MAX(2, numberOfLines), bounds.size.height);
    
    // Adjust the text to be in the center vertically, if the text size is smaller than bounds
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self.attributedText);
    
    CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, (CFIndex)[self.attributedText length]), NULL, textRect.size, NULL);
    textSize = CGSizeMake(ceil(textSize.width), ceil(textSize.height)); // Fix for iOS 4, CTFramesetterSuggestFrameSizeWithConstraints sometimes returns fractional sizes
    
    if (textSize.height < bounds.size.height) {
        CGFloat yOffset = 0.0f;
        switch (0) {
            case 0:
                yOffset = floor((bounds.size.height - textSize.height) / 2.0f);
                break;
            case 1:
                yOffset = bounds.size.height - textSize.height;
                break;
            case 2:
            default:
                break;
        }
        
        textRect.origin.y += yOffset;
    }
    CFRelease(framesetter);
    return textRect;
}

static inline CGFloat TTTFlushFactorForTextAlignment(NSTextAlignment textAlignment) {
    switch (textAlignment) {
        case NSTextAlignmentCenter:
            return 0.5f;
        case NSTextAlignmentRight:
            return 1.0f;
        case NSTextAlignmentLeft:
        default:
            return 0.0f;
    }
}


@end
